Using jsPsych and cognition.run

Session 2 - intermediate


Translations available

Disclaimer: may not be very accurate…


Worksheet overview

Aims

By the end of this worksheet you should be able to:

  • program your own experiments in jsPsych
  • host the experiment online using cognition.run
  • use the participant data for analysis
  • apply the basic skills you have learnt for your own purposes
  • learn some extra skills such as HTML, javascript, CSS and JSON

Pre-requisites

To complete the aims you will need to:

  • follow this worksheet
  • ask questions if you are not sure/be able to google
  • have a working computer and internet connection
  • be patient when things do not work

You do not need to:

  • have any programming knowledge
  • have high computer literacy
  • know anything about jsPsych, cognition.run, html, css or javascript
  • be a linguist

Structure

The worksheet will go through the following sections:

  • Building a lexical decision experiment

    • using a keyboard response plugin
    • customising the stimuli with CSS
    • customising the plugin parameters
    • combining trials into a timeline

  • Timeline variables

    • how to use them
    • adding variables to your results data
    • formatting files from csv to javascript/json in R

  • Completing your experiment

    • adding instruction and consent pages
    • collecting demographic information
    • adding random participant IDs

Recap

In the last session we should have:

  • set up an account for cognition.run
  • become familiar with the cognition.run interface
  • become familiar with JsPsych
  • learnt what a plugin is and how they work
  • built a very simple experiment

Building a lexical decision experiment

One of the most common measurements collected in experiments are reaction times (rt). In this section we will be using a keyboard response plugin, i.e. where the participant presses a specific key on their keyboard in response to a stimuli in order to measure how long it takes the participant to respond.

The paradigm that we will use to practice using this plugin will be the lexical decision task, where a word or non-word will be presented on the screen and the participant has to respond to the stimuli with one of two key options, they will press key1 if they think the word is real, key2 if they think the word is not real.

the html-keyboard-response plugin

The basic structure of the html-keyboard-response plugin is:

var my_keyboard_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'my_stimulus',
  choices: ['key1', 'key2', 'key_n']
}

If we used this in a full experiment in cognition.run, we would just see my_stimulus on the screen and it would just wait for a valid keyboard response from the participant.

  • The type parameter declares which plugin to use, here we are using the jsPsychHtmlKeyboardResponse plugin

  • The stimulus parameter declares what stimulus to present, here we just use the string 'my_stimulus'

  • The choices parameter declares which keys are valid for the trial, here we use an array, as shown by the [ and ] at the start and end. The strings 'key1', 'key2' and 'key_n' are not actually valid keys and would not be recognised by jspsych, they are just for generalisation. Instead, we would probably use something like ['a', 'b', 'c'] if we wanted to allow responses only from the keys a, b and c.

    Note
    If you want to allow all possible keys, we would use choices: 'ALL_KEYS'.

    If you want to prevent all keys from being allowed, we would use choices: 'NO_KEYS'.

    If you are unsure how to specify the key you want, refer to https://docs.google.com/spreadsheets/d/19v9OGreNGmmOAacJ33cSrouNO_OfBxqa4hiXTwex5dY/edit?usp=sharing

For this tutorial, we will make the following trial as the basis for our lexical decision experiment:

var my_keyboard_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'word',
  choices: ['d', 'h']
}

We now have the stimulus as word and the choices as d or h.

Remember, that if we want to preview all of our experiment in cognition.run, we need to have a fully working jspsych code, so the full code we would need is:

// --------
// Title: lexical decision experiment
// jsPsych version: 7.3.1
// date: [today]
// author: [your name]
//----------

var jsPsych = initJsPsych();

var my_keyboard_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'word',
  choices: ['d', 'h']
}

var timeline = [];

timeline.push(my_keyboard_trial);

jsPsych.run(timeline);

This is what it should look like in cognition.run:

customising the stimulus with CSS

Our stimulus may not look how we want it to look, e.g. the font, colour, size etc might not be the preferred style for our experiment.

This is because jspsych has certain default values for the appearance of things like text. But, it can easily be modified with some extra code.

This is based on CSS (Cascading Style Sheets), which allows html to be styled in a specific way. We will cover CSS in more detail later, but it is quite a large topic and is a separate language to html and javascript, so it is not within the scope of this level of tutorial to cover it thoroughly now.

What we will do is apply some basic CSS styling to our stimulus, to demonstrate how to customise some aspects of the appearance. Crucially, this can be done within some basic html code. This keeps things simple.

Currently, our stimulus is 'word', which is presented with the following defaults:

  • font-family: Arial;
  • font-size: 18px;
  • color: black;
  • font-weight: normal;

If we want to change any of this, or indeed anything else about the appearance, we need to add some inline css to the stimulus.

We can do this by modifying the stimulus to look more like html with css:

stimulus: '<div>word</div>

You will see the added <div> and </div> at the start and end of the word.

This is html.

It identifies the text as a content division element. Note that there is <div> at the start to indicate the start of the content and a </div> a the end to indicate the end of the content. Nothing really changes at the moment as jspsych already defines the text in this way by default.

However, what we can now do is add css to our div. We do this in the following way:

stimulus: '<div style="color:red;">word</div>

Now we have added some css to our html object. This is done by the style="color:red" part.

When adding css directly to html in this way, we first have to declare that we want to style the specific element, in this example the text contained in our div. Therefore it goes within the starting <div>.

It is evaluated as css by the style function, with different elements of the styling being declared within the " " after the =.

In the above example, we change the css of the text color to red. Note the ; which we use to indicate the end of the color parameter changes.

We can now use this basic structure to change other parameters related to the styling of the text in our div element.

stimulus: '<div style="color:red; background: pink; font-size: 30pt; font-family: monospace; font-weight: bold; position: absolute; top: 0px; left: 0;">word</div>

Now we have the following parameters declared:

  • colour: red makes the text color red, you can use color names or hex codes e.g. #00000 is black
  • background: pink makes the text background pink, again you can use color names or hex codes
  • font-size: 30pt makes the text size 30 point, you can also specify other units
  • font-family: monospace makes the font monospace, other web safe fonts can be used
  • font-weight: bold makes the font bold, other weights can be used
  • position: absolute makes the positioning of the text absolute in relation to the screen, it needs additional parameters to be specified such as top, right, bottom or left
  • top: 0px makes the absolute positioning to be 0px from the top
  • left: 0px makes the absolute positioning to be 0px from the left

This is what it should like:

customising the plugin parameters

As we do not really need much styling to our stimuli, we will return to the original defaults of:

var my_keyboard_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'word',
  choices: ['d', 'h']
}

However, what we can do is change specific parameters of our trial.

If we refer to the plugin documentation on the jspsych website - https://www.jspsych.org/7.0/plugins/html-keyboard-response/ we can see that there are many other parameters that we can modify with this plugin.

prompt

We might for example want to include a prompt if the trials are practice trials, so the participant can be reminded of which keys to press:

var my_keyboard_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'word',
  choices: ['d', 'h'],
  prompt: 'press "d" if you think it is a real word, press "h" if you think it is not a real word'
}

As you can see, the prompt is displayed in a quite inconvenient position. So we can now modify this using the html and css code we used previously:

var my_keyboard_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'word',
  choices: ['d', 'h'],
  prompt: '<div style="font-size:10pt; position:relative; bottom:150px;">press "d" if you think it is a real word, press "h" if you think it is not a real word</div>'
}

Now, we have changed the font size to 10pt so it is smaller than the stimulus text.

We have also change the position, this time using position:relative; bottom:150px, unlike in the previous example where we used position:absolute, the use of relative means we move relative to where it was originally placed, so in this example we move it 150px from the bottom, i.e. 150px vertically upwards from the bottom.

This is how it should now look:

stimulus_duration

We might also want to only show the stimulus on the screen for a specified amount of time. To do this, we can add the stimulus_duration parameter:

var my_keyboard_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'word',
  choices: ['d', 'h'],
  prompt: '<div style="font-size:10pt; position:relative; bottom:150px;">press "d" if you think it is a real word, press "h" if you think it is not a real word</div>',
  stimulus_duration: 500
}

In the example above, we have set this parameter to 500, which means that the stimulus will be shown only for 500ms. Note that the trial does not finish until a response has been made.

trial_duration

If we want to make the trial end after a specified time, we can also add the trial_duration parameter:

var my_keyboard_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'word',
  choices: ['d', 'h'],
  prompt: '<div style="font-size:10pt; position:relative; bottom:150px;">press "d" if you think it is a real word, press "h" if you think it is not a real word</div>',
  stimulus_duration: 500,
  trial_duration: 1000
}

Now, the trial will end after 1000ms or if there is a response by the participant in within the specified time.

This is how it should look:

combining trials into a timeline

At the moment our experiment comprises of just one trial…, which is not particularly useful. However, what we have covered so far should allow us to put together the building blocks of an experiment.

In this section we will combine multiple trials into a timeline that is nested within our main timeline.

This will involve adding a new variable that has a fixation trial, so that our lexical decision trial is preceeded by this fixation trial.

Lets first make the lexical decision trial, we will keep it as just a keyboard response with no duration parameters:

var lexical_decision_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'word',
  choices: ['d', 'h'],
  prompt: '<div style="font-size:10pt; position:relative; bottom:150px;">press "d" if you think it is a real word, press "h" if you think it is not a real word</div>'
}

Now we will make the fixation trial, which will show a + stimulus on the screen for a set amount of time, in this case 2000ms.

Note the font-size is set to 30pt and the choices is NO_KEYS, this mean we will just see the + on the screen and there will be no keys that end the trial, just the time limit of 2000ms:

var fixation_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: '<div style="font-size:30pt;">+</div>',
  choices: 'NO_KEYS',
  prompt: '<div style="font-size:10pt; position:relative; bottom:150px;"><br/></div>',
  trial_duration: 2000
}

We can now make a new variable called lexical_decision_combined, this will combine the fixation_trial and the lexical_decision_trial in a specified order, within a specified timeline.

We do this using the timeline parameter, which we can specify within a standard var.

We assign the timeline an array, [fixation_trial, lexical_decision_trial], which means that the timeline will first show the fixation_trial, then the lexical_decision_trial. If we changed the positions of the items in the array this will change the order of the presentation.

var lexical_decision_combined = {
  timeline: [fixation_trial, lexical_decision_trial]
}

If we now push lexical_decision_combined to our timeline, using the timeline.push, we should see that it presents the two trials together.

This is how your code should look so far:

// --------
// Title: Demo experiment
// jsPsych version: 7.3.1
// date: [today]
// author: [your name]
//----------

var jsPsych = initJsPsych();

var fixation_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: '<div style="font-size:30pt;">+</div>',
  choices: 'NO_KEYS',
  prompt: '<div style="font-size:10pt; position:relative; bottom:150px;"><br/></div>',
  trial_duration: 2000
}

var lexical_decision_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'word',
  choices: ['d', 'h'],
  prompt: '<div style="font-size:10pt; position:relative; bottom:150px;">press "d" if you think it is a real word, press "h" if you think it is not a real word</div>'
};

var lexical_decision_combined = {
  timeline: [fixation_trial, lexical_decision_trial]
}


var timeline = [];
timeline.push(lexical_decision_combined);

jsPsych.run(timeline);
LS0tCnRpdGxlOiAiVXNpbmcganNQc3ljaCBhbmQgY29nbml0aW9uLnJ1biIKc3VidGl0bGU6ICJTZXNzaW9uIDIgLSBpbnRlcm1lZGlhdGUiCmF1dGhvcjogIkphbWVzIEJyYW5kIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVknKWAiCm91dHB1dDoKICBybWRmb3JtYXRzOjpyZWFkdGhlZG93bjoKICAgIHBhbmRvY19hcmdzOiAiLS1oaWdobGlnaHQtc3R5bGU9bXkudGhlbWUiCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgY29sbGFwc2VkOiBmYWxzZQogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICBsaWdodGJveDogVFJVRQogICAgZ2FsbGVyeTogVFJVRQogICAgY3NzOiAiY3NzL3N0eWxlLmNzcyIKICAgIAotLS0KCmBgYHtyIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoc2xpY2tSKQpsaWJyYXJ5KGh0bWx0b29scykKbGlicmFyeSh4YXJpbmdhbkV4dHJhKQpsaWJyYXJ5KHJtYXJrZG93bikKbGlicmFyeShmb250YXdlc29tZSkKbGlicmFyeShic3BsdXMpCgpgYGAKCmBgYHtyIHNldHVwLCB3YXJuaW5nPUZBTFNFLCBlY2hvPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBldmFsID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICBjb21tZW50ID0gTkEpCgprbml0cjo6a25pdF9ob29rcyRzZXQoCiAgbWVzc2FnZSA9IGZ1bmN0aW9uKHgsIG9wdGlvbnMpIHsKICAgICBwYXN0ZSgnPGJ1dHRvbiB0eXBlPSJidXR0b24iIGNsYXNzPSJjb2xsYXBzaWJsZTEiPjxzdHJvbmc+JywKICAgICBmYShuYW1lID0gImNpcmNsZS1pbmZvIiksCiAgICAgJyBtb3JlIGluZm88L3N0cm9uZz48L2J1dHRvbj4nLCAnPGRpdiBjbGFzcz0iY29udGVudDEiPjxwPicsCiAgICAgZ3N1YignIyMnLCAnXG4nLCB4KSwKICAgICAnPC9wPjwvZGl2PicsCiAgICAgc2VwID0gJ1xuJykKICAgfSkKCmNvZGVibG9jayA9IGZ1bmN0aW9uKHgsIG9wdGlvbnMpIHsKICAgICBjYXQocGFzdGUoJzxkaXYgY2xhc3M9ImNvZGVibG9jayI+JywKICAgICBwYXN0ZTAoeCksCiAgICAgJzwvZGl2PicsCiAgICAgc2VwID0gJ1xuJykpCiAgIH0KCmBgYAoKLS0tCgojIyBgciBmYSgibGFuZ3VhZ2UiKWAgVHJhbnNsYXRpb25zIGF2YWlsYWJsZQoKRGlzY2xhaW1lcjogbWF5IG5vdCBiZSB2ZXJ5IGFjY3VyYXRlLi4uCgo8ZGl2IGlkPSJnb29nbGVfdHJhbnNsYXRlX2VsZW1lbnQiPjwvZGl2PgoKLS0tCgojIFdvcmtzaGVldCBvdmVydmlldwoKIyMgYHIgZmEoImNyb3NzaGFpcnMiKWAgQWltcwoKQnkgdGhlIGVuZCBvZiB0aGlzIHdvcmtzaGVldCB5b3Ugc2hvdWxkIGJlIGFibGUgdG86CgotICoqcHJvZ3JhbSoqIHlvdXIgb3duIGV4cGVyaW1lbnRzIGluIGpzUHN5Y2gKLSAqKmhvc3QqKiB0aGUgZXhwZXJpbWVudCBvbmxpbmUgdXNpbmcgY29nbml0aW9uLnJ1bgotICoqdXNlKiogdGhlIHBhcnRpY2lwYW50IGRhdGEgZm9yIGFuYWx5c2lzCi0gKiphcHBseSoqIHRoZSBiYXNpYyBza2lsbHMgeW91IGhhdmUgbGVhcm50IGZvciB5b3VyIG93biBwdXJwb3NlcwotICoqbGVhcm4qKiBzb21lIGV4dHJhIHNraWxscyBzdWNoIGFzIEhUTUwsIGphdmFzY3JpcHQsIENTUyBhbmQgSlNPTgoKIyMgYHIgZmEoInVzZXItZ3JhZHVhdGUiKWAgUHJlLXJlcXVpc2l0ZXMKClRvIGNvbXBsZXRlIHRoZSBhaW1zIHlvdSB3aWxsIG5lZWQgdG86CgotICoqZm9sbG93KiogdGhpcyB3b3Jrc2hlZXQKLSAqKmFzayoqIHF1ZXN0aW9ucyBpZiB5b3UgYXJlIG5vdCBzdXJlL2JlIGFibGUgdG8gZ29vZ2xlCi0gKipoYXZlKiogYSB3b3JraW5nIGNvbXB1dGVyIGFuZCBpbnRlcm5ldCBjb25uZWN0aW9uCi0gKipiZSBwYXRpZW50Kiogd2hlbiB0aGluZ3MgZG8gbm90IHdvcmsKCllvdSBkbyBub3QgbmVlZCB0bzoKCi0gaGF2ZSBhbnkgKipwcm9ncmFtbWluZyBrbm93bGVkZ2UqKgotIGhhdmUgaGlnaCAqKmNvbXB1dGVyIGxpdGVyYWN5KioKLSBrbm93IGFueXRoaW5nIGFib3V0ICoqanNQc3ljaCwgY29nbml0aW9uLnJ1biwgaHRtbCwgY3NzIG9yIGphdmFzY3JpcHQqKgotIGJlIGEgKipsaW5ndWlzdCoqCgojIyBgciBmYSgiZm9sZGVyLXRyZWUiKWAgU3RydWN0dXJlCgpUaGUgd29ya3NoZWV0IHdpbGwgZ28gdGhyb3VnaCB0aGUgZm9sbG93aW5nIHNlY3Rpb25zOgoKLSBCdWlsZGluZyBhIGxleGljYWwgZGVjaXNpb24gZXhwZXJpbWVudAoKICAgIC0gdXNpbmcgYSBrZXlib2FyZCByZXNwb25zZSBwbHVnaW4KICAgIC0gY3VzdG9taXNpbmcgdGhlIHN0aW11bGkgd2l0aCBDU1MKICAgIC0gY3VzdG9taXNpbmcgdGhlIHBsdWdpbiBwYXJhbWV0ZXJzCiAgICAtIGNvbWJpbmluZyB0cmlhbHMgaW50byBhIHRpbWVsaW5lCjxici8+PGJyLz4KLSBUaW1lbGluZSB2YXJpYWJsZXMKCiAgICAtIGhvdyB0byB1c2UgdGhlbQogICAgLSBhZGRpbmcgdmFyaWFibGVzIHRvIHlvdXIgcmVzdWx0cyBkYXRhCiAgICAtIGZvcm1hdHRpbmcgZmlsZXMgZnJvbSBjc3YgdG8gamF2YXNjcmlwdC9qc29uIGluIFIKPGJyLz48YnIvPgotIENvbXBsZXRpbmcgeW91ciBleHBlcmltZW50CgogICAgLSBhZGRpbmcgaW5zdHJ1Y3Rpb24gYW5kIGNvbnNlbnQgcGFnZXMKICAgIC0gY29sbGVjdGluZyBkZW1vZ3JhcGhpYyBpbmZvcm1hdGlvbgogICAgLSBhZGRpbmcgcmFuZG9tIHBhcnRpY2lwYW50IElEcwoKPCEtLSAjIyBgciBmYSgiYXJyb3ctcmlnaHQiKWAgVXAgbmV4dCAtLT4KCjwhLS0gSW4gdGhlIG5leHQgc2Vzc2lvbnMgd2Ugd2lsbCBjb3ZlcjogLS0+Cgo8IS0tIC0gYnVpbGRpbmcgYSBwcm9wZXIgZXhwZXJpbWVudCAtLT4KPCEtLSAtIHVzaW5nIHN0aW11bGkgZmlsZXMgLS0+CjwhLS0gLSBjdXN0b21pc2luZyBwbHVnaW5zIC0tPgo8IS0tIC0gY3VzdG9taXNpbmcgYXBwZWFyYW5jZSAtLT4KPCEtLSAtIHdvcmtpbmcgd2l0aCB0aGUgZGF0YSAtLT4KCiMjIGByIGZhKCJsaWdodGJ1bGIiKWAgUmVjYXAKCkluIHRoZSBsYXN0IHNlc3Npb24gd2Ugc2hvdWxkIGhhdmU6CgotIHNldCB1cCBhbiBhY2NvdW50IGZvciBjb2duaXRpb24ucnVuCi0gYmVjb21lIGZhbWlsaWFyIHdpdGggdGhlIGNvZ25pdGlvbi5ydW4gaW50ZXJmYWNlCi0gYmVjb21lIGZhbWlsaWFyIHdpdGggSnNQc3ljaAotIGxlYXJudCB3aGF0IGEgcGx1Z2luIGlzIGFuZCBob3cgdGhleSB3b3JrCi0gYnVpbHQgYSB2ZXJ5IHNpbXBsZSBleHBlcmltZW50CgotLS0KCiMgQnVpbGRpbmcgYSBsZXhpY2FsIGRlY2lzaW9uIGV4cGVyaW1lbnQKCk9uZSBvZiB0aGUgbW9zdCBjb21tb24gbWVhc3VyZW1lbnRzIGNvbGxlY3RlZCBpbiBleHBlcmltZW50cyBhcmUgcmVhY3Rpb24gdGltZXMgKHJ0KS4gSW4gdGhpcyBzZWN0aW9uIHdlIHdpbGwgYmUgdXNpbmcgYSBrZXlib2FyZCByZXNwb25zZSBwbHVnaW4sIGkuZS4gd2hlcmUgdGhlIHBhcnRpY2lwYW50IHByZXNzZXMgYSBzcGVjaWZpYyBrZXkgb24gdGhlaXIga2V5Ym9hcmQgaW4gcmVzcG9uc2UgdG8gYSBzdGltdWxpIGluIG9yZGVyIHRvIG1lYXN1cmUgaG93IGxvbmcgaXQgdGFrZXMgdGhlIHBhcnRpY2lwYW50IHRvIHJlc3BvbmQuCgpUaGUgcGFyYWRpZ20gdGhhdCB3ZSB3aWxsIHVzZSB0byBwcmFjdGljZSB1c2luZyB0aGlzIHBsdWdpbiB3aWxsIGJlIHRoZSAqKmxleGljYWwgZGVjaXNpb24gdGFzayoqLCB3aGVyZSBhIHdvcmQgb3Igbm9uLXdvcmQgd2lsbCBiZSBwcmVzZW50ZWQgb24gdGhlIHNjcmVlbiBhbmQgdGhlIHBhcnRpY2lwYW50IGhhcyB0byByZXNwb25kIHRvIHRoZSBzdGltdWxpIHdpdGggb25lIG9mIHR3byBrZXkgb3B0aW9ucywgdGhleSB3aWxsIHByZXNzIGBrZXkxYCBpZiB0aGV5IHRoaW5rIHRoZSB3b3JkIGlzIHJlYWwsIGBrZXkyYCBpZiB0aGV5IHRoaW5rIHRoZSB3b3JkIGlzIG5vdCByZWFsLgoKIyMgdGhlIGh0bWwta2V5Ym9hcmQtcmVzcG9uc2UgcGx1Z2luCgpUaGUgYmFzaWMgc3RydWN0dXJlIG9mIHRoZSBgaHRtbC1rZXlib2FyZC1yZXNwb25zZWAgcGx1Z2luIGlzOgoKYGBge2pzfQp2YXIgbXlfa2V5Ym9hcmRfdHJpYWwgPSB7CiAgdHlwZToganNQc3ljaEh0bWxLZXlib2FyZFJlc3BvbnNlLAogIHN0aW11bHVzOiAnbXlfc3RpbXVsdXMnLAogIGNob2ljZXM6IFsna2V5MScsICdrZXkyJywgJ2tleV9uJ10KfQoKYGBgCgpJZiB3ZSB1c2VkIHRoaXMgaW4gYSBmdWxsIGV4cGVyaW1lbnQgaW4gY29nbml0aW9uLnJ1biwgd2Ugd291bGQganVzdCBzZWUgYG15X3N0aW11bHVzYCBvbiB0aGUgc2NyZWVuIGFuZCBpdCB3b3VsZCBqdXN0IHdhaXQgZm9yIGEgdmFsaWQga2V5Ym9hcmQgcmVzcG9uc2UgZnJvbSB0aGUgcGFydGljaXBhbnQuCgotIFRoZSBgdHlwZWAgcGFyYW1ldGVyIGRlY2xhcmVzIHdoaWNoIHBsdWdpbiB0byB1c2UsIGhlcmUgd2UgYXJlIHVzaW5nIHRoZSBganNQc3ljaEh0bWxLZXlib2FyZFJlc3BvbnNlYCBwbHVnaW4KCi0gVGhlIGBzdGltdWx1c2AgcGFyYW1ldGVyIGRlY2xhcmVzIHdoYXQgc3RpbXVsdXMgdG8gcHJlc2VudCwgaGVyZSB3ZSBqdXN0IHVzZSB0aGUgc3RyaW5nIGAnbXlfc3RpbXVsdXMnYAoKLSBUaGUgYGNob2ljZXNgIHBhcmFtZXRlciBkZWNsYXJlcyB3aGljaCBrZXlzIGFyZSB2YWxpZCBmb3IgdGhlIHRyaWFsLCBoZXJlIHdlIHVzZSBhbiBhcnJheSwgYXMgc2hvd24gYnkgdGhlIGBbYCBhbmQgYF1gIGF0IHRoZSBzdGFydCBhbmQgZW5kLiBUaGUgc3RyaW5ncyBgJ2tleTEnYCwgYCdrZXkyJ2AgYW5kIGAna2V5X24nYCBhcmUgbm90IGFjdHVhbGx5IHZhbGlkIGtleXMgYW5kIHdvdWxkIG5vdCBiZSByZWNvZ25pc2VkIGJ5IGpzcHN5Y2gsIHRoZXkgYXJlIGp1c3QgZm9yIGdlbmVyYWxpc2F0aW9uLiBJbnN0ZWFkLCB3ZSB3b3VsZCBwcm9iYWJseSB1c2Ugc29tZXRoaW5nIGxpa2UgYFsnYScsICdiJywgJ2MnXWAgaWYgd2Ugd2FudGVkIHRvIGFsbG93IHJlc3BvbnNlcyBvbmx5IGZyb20gdGhlIGtleXMgYGFgLCBgYmAgYW5kIGBjYC48YnIvPjxici8+KipOb3RlKio8YnIvPklmIHlvdSB3YW50IHRvIGFsbG93ICoqYWxsIHBvc3NpYmxlIGtleXMqKiwgd2Ugd291bGQgdXNlIGBjaG9pY2VzOiAnQUxMX0tFWVMnYC48YnIvPjxici8+SWYgeW91IHdhbnQgdG8gKipwcmV2ZW50IGFsbCBrZXlzKiogZnJvbSBiZWluZyBhbGxvd2VkLCB3ZSB3b3VsZCB1c2UgYGNob2ljZXM6ICdOT19LRVlTJ2AuPGJyLz48YnIvPklmIHlvdSBhcmUgdW5zdXJlIGhvdyB0byBzcGVjaWZ5IHRoZSBrZXkgeW91IHdhbnQsIHJlZmVyIHRvIGh0dHBzOi8vZG9jcy5nb29nbGUuY29tL3NwcmVhZHNoZWV0cy9kLzE5djlPR3JlTkdtbU9BYWNKMzNjU3JvdU5PX09mQnhxYTRoaVhUd2V4NWRZL2VkaXQ/dXNwPXNoYXJpbmcKCkZvciB0aGlzIHR1dG9yaWFsLCB3ZSB3aWxsIG1ha2UgdGhlIGZvbGxvd2luZyB0cmlhbCBhcyB0aGUgYmFzaXMgZm9yIG91ciBsZXhpY2FsIGRlY2lzaW9uIGV4cGVyaW1lbnQ6CgpgYGB7anN9CnZhciBteV9rZXlib2FyZF90cmlhbCA9IHsKICB0eXBlOiBqc1BzeWNoSHRtbEtleWJvYXJkUmVzcG9uc2UsCiAgc3RpbXVsdXM6ICd3b3JkJywKICBjaG9pY2VzOiBbJ2QnLCAnaCddCn0KYGBgCgpXZSBub3cgaGF2ZSB0aGUgc3RpbXVsdXMgYXMgYHdvcmRgIGFuZCB0aGUgY2hvaWNlcyBhcyBgZGAgb3IgYGhgLgoKUmVtZW1iZXIsIHRoYXQgaWYgd2Ugd2FudCB0byBwcmV2aWV3IGFsbCBvZiBvdXIgZXhwZXJpbWVudCBpbiBjb2duaXRpb24ucnVuLCB3ZSBuZWVkIHRvIGhhdmUgYSBmdWxseSB3b3JraW5nIGpzcHN5Y2ggY29kZSwgc28gdGhlIGZ1bGwgY29kZSB3ZSB3b3VsZCBuZWVkIGlzOgoKYGBge2pzfQovLyAtLS0tLS0tLQovLyBUaXRsZTogbGV4aWNhbCBkZWNpc2lvbiBleHBlcmltZW50Ci8vIGpzUHN5Y2ggdmVyc2lvbjogNy4zLjEKLy8gZGF0ZTogW3RvZGF5XQovLyBhdXRob3I6IFt5b3VyIG5hbWVdCi8vLS0tLS0tLS0tLQoKdmFyIGpzUHN5Y2ggPSBpbml0SnNQc3ljaCgpOwoKdmFyIG15X2tleWJvYXJkX3RyaWFsID0gewogIHR5cGU6IGpzUHN5Y2hIdG1sS2V5Ym9hcmRSZXNwb25zZSwKICBzdGltdWx1czogJ3dvcmQnLAogIGNob2ljZXM6IFsnZCcsICdoJ10KfQoKdmFyIHRpbWVsaW5lID0gW107Cgp0aW1lbGluZS5wdXNoKG15X2tleWJvYXJkX3RyaWFsKTsKCmpzUHN5Y2gucnVuKHRpbWVsaW5lKTsKCmBgYAoKVGhpcyBpcyB3aGF0IGl0IHNob3VsZCBsb29rIGxpa2UgaW4gY29nbml0aW9uLnJ1bjoKCiFbXShpbWFnZXMvd29yZF90cmlhbC5wbmcpCgojIyBjdXN0b21pc2luZyB0aGUgc3RpbXVsdXMgd2l0aCBDU1MKCk91ciBzdGltdWx1cyBtYXkgbm90IGxvb2sgaG93IHdlIHdhbnQgaXQgdG8gbG9vaywgZS5nLiB0aGUgZm9udCwgY29sb3VyLCBzaXplIGV0YyBtaWdodCBub3QgYmUgdGhlIHByZWZlcnJlZCBzdHlsZSBmb3Igb3VyIGV4cGVyaW1lbnQuCgpUaGlzIGlzIGJlY2F1c2UganNwc3ljaCBoYXMgY2VydGFpbiBkZWZhdWx0IHZhbHVlcyBmb3IgdGhlIGFwcGVhcmFuY2Ugb2YgdGhpbmdzIGxpa2UgdGV4dC4gQnV0LCBpdCBjYW4gZWFzaWx5IGJlIG1vZGlmaWVkIHdpdGggc29tZSBleHRyYSBjb2RlLgoKVGhpcyBpcyBiYXNlZCBvbiBDU1MgKENhc2NhZGluZyBTdHlsZSBTaGVldHMpLCB3aGljaCBhbGxvd3MgaHRtbCB0byBiZSBzdHlsZWQgaW4gYSBzcGVjaWZpYyB3YXkuIFdlIHdpbGwgY292ZXIgQ1NTIGluIG1vcmUgZGV0YWlsIGxhdGVyLCBidXQgaXQgaXMgcXVpdGUgYSBsYXJnZSB0b3BpYyBhbmQgaXMgYSBzZXBhcmF0ZSBsYW5ndWFnZSB0byBodG1sIGFuZCBqYXZhc2NyaXB0LCBzbyBpdCBpcyBub3Qgd2l0aGluIHRoZSBzY29wZSBvZiB0aGlzIGxldmVsIG9mIHR1dG9yaWFsIHRvIGNvdmVyIGl0IHRob3JvdWdobHkgbm93LgoKV2hhdCB3ZSB3aWxsIGRvIGlzIGFwcGx5IHNvbWUgYmFzaWMgQ1NTIHN0eWxpbmcgdG8gb3VyIHN0aW11bHVzLCB0byBkZW1vbnN0cmF0ZSBob3cgdG8gY3VzdG9taXNlIHNvbWUgYXNwZWN0cyBvZiB0aGUgYXBwZWFyYW5jZS4gQ3J1Y2lhbGx5LCB0aGlzIGNhbiBiZSBkb25lIHdpdGhpbiBzb21lIGJhc2ljIGh0bWwgY29kZS4gVGhpcyBrZWVwcyB0aGluZ3Mgc2ltcGxlLgoKQ3VycmVudGx5LCBvdXIgc3RpbXVsdXMgaXMgYCd3b3JkJ2AsIHdoaWNoIGlzIHByZXNlbnRlZCB3aXRoIHRoZSBmb2xsb3dpbmcgZGVmYXVsdHM6CgotIGZvbnQtZmFtaWx5OiBBcmlhbDsKLSBmb250LXNpemU6IDE4cHg7Ci0gY29sb3I6IGJsYWNrOwotIGZvbnQtd2VpZ2h0OiBub3JtYWw7CgpJZiB3ZSB3YW50IHRvIGNoYW5nZSBhbnkgb2YgdGhpcywgb3IgaW5kZWVkIGFueXRoaW5nIGVsc2UgYWJvdXQgdGhlIGFwcGVhcmFuY2UsIHdlIG5lZWQgdG8gYWRkIHNvbWUgaW5saW5lIGNzcyB0byB0aGUgc3RpbXVsdXMuCgpXZSBjYW4gZG8gdGhpcyBieSBtb2RpZnlpbmcgdGhlIHN0aW11bHVzIHRvIGxvb2sgbW9yZSBsaWtlIGh0bWwgd2l0aCBjc3M6CgpgYGB7anN9CnN0aW11bHVzOiAnPGRpdj53b3JkPC9kaXY+CgpgYGAKCllvdSB3aWxsIHNlZSB0aGUgYWRkZWQgYDxkaXY+YCBhbmQgYDwvZGl2PmAgYXQgdGhlIHN0YXJ0IGFuZCBlbmQgb2YgdGhlIHdvcmQuCgpUaGlzIGlzIGh0bWwuCgpJdCBpZGVudGlmaWVzIHRoZSB0ZXh0IGFzIGEgYGNvbnRlbnQgZGl2aXNpb24gZWxlbWVudGAuIE5vdGUgdGhhdCB0aGVyZSBpcyBgPGRpdj5gIGF0IHRoZSBzdGFydCB0byBpbmRpY2F0ZSB0aGUgc3RhcnQgb2YgdGhlIGNvbnRlbnQgYW5kIGEgYDwvZGl2PmAgYSB0aGUgZW5kIHRvIGluZGljYXRlIHRoZSBlbmQgb2YgdGhlIGNvbnRlbnQuIE5vdGhpbmcgcmVhbGx5IGNoYW5nZXMgYXQgdGhlIG1vbWVudCBhcyBqc3BzeWNoIGFscmVhZHkgZGVmaW5lcyB0aGUgdGV4dCBpbiB0aGlzIHdheSBieSBkZWZhdWx0LgoKSG93ZXZlciwgd2hhdCB3ZSBjYW4gbm93IGRvIGlzIGFkZCBjc3MgdG8gb3VyIGRpdi4gV2UgZG8gdGhpcyBpbiB0aGUgZm9sbG93aW5nIHdheToKCmBgYHtqc30Kc3RpbXVsdXM6ICc8ZGl2IHN0eWxlPSJjb2xvcjpyZWQ7Ij53b3JkPC9kaXY+CgpgYGAKCk5vdyB3ZSBoYXZlIGFkZGVkIHNvbWUgY3NzIHRvIG91ciBodG1sIG9iamVjdC4gVGhpcyBpcyBkb25lIGJ5IHRoZSBgc3R5bGU9ImNvbG9yOnJlZCJgIHBhcnQuCgpXaGVuIGFkZGluZyBjc3MgZGlyZWN0bHkgdG8gaHRtbCBpbiB0aGlzIHdheSwgd2UgZmlyc3QgaGF2ZSB0byBkZWNsYXJlIHRoYXQgd2Ugd2FudCB0byBzdHlsZSB0aGUgc3BlY2lmaWMgZWxlbWVudCwgaW4gdGhpcyBleGFtcGxlIHRoZSB0ZXh0IGNvbnRhaW5lZCBpbiBvdXIgYGRpdmAuIFRoZXJlZm9yZSBpdCBnb2VzIHdpdGhpbiB0aGUgc3RhcnRpbmcgYDxkaXY+YC4KCkl0IGlzIGV2YWx1YXRlZCBhcyBjc3MgYnkgdGhlIGBzdHlsZWAgZnVuY3Rpb24sIHdpdGggZGlmZmVyZW50IGVsZW1lbnRzIG9mIHRoZSBzdHlsaW5nIGJlaW5nIGRlY2xhcmVkIHdpdGhpbiB0aGUgYCIgImAgYWZ0ZXIgdGhlIGA9YC4KCkluIHRoZSBhYm92ZSBleGFtcGxlLCB3ZSBjaGFuZ2UgdGhlIGNzcyBvZiB0aGUgdGV4dCBgY29sb3JgIHRvIGByZWRgLiBOb3RlIHRoZSBgO2Agd2hpY2ggd2UgdXNlIHRvIGluZGljYXRlIHRoZSBlbmQgb2YgdGhlIGNvbG9yIHBhcmFtZXRlciBjaGFuZ2VzLgoKV2UgY2FuIG5vdyB1c2UgdGhpcyBiYXNpYyBzdHJ1Y3R1cmUgdG8gY2hhbmdlIG90aGVyIHBhcmFtZXRlcnMgcmVsYXRlZCB0byB0aGUgc3R5bGluZyBvZiB0aGUgdGV4dCBpbiBvdXIgZGl2IGVsZW1lbnQuCgpgYGB7anN9CnN0aW11bHVzOiAnPGRpdiBzdHlsZT0iY29sb3I6cmVkOyBiYWNrZ3JvdW5kOiBwaW5rOyBmb250LXNpemU6IDMwcHQ7IGZvbnQtZmFtaWx5OiBtb25vc3BhY2U7IGZvbnQtd2VpZ2h0OiBib2xkOyBwb3NpdGlvbjogYWJzb2x1dGU7IHRvcDogMHB4OyBsZWZ0OiAwOyI+d29yZDwvZGl2PgoKYGBgCgpOb3cgd2UgaGF2ZSB0aGUgZm9sbG93aW5nIHBhcmFtZXRlcnMgZGVjbGFyZWQ6CgotIGBjb2xvdXI6IHJlZGAgbWFrZXMgdGhlIHRleHQgY29sb3IgcmVkLCB5b3UgY2FuIHVzZSBbY29sb3IgbmFtZXNdKGh0dHBzOi8vaHRtbGNvbG9yY29kZXMuY29tL2NvbG9yLW5hbWVzLykgb3IgW2hleCBjb2Rlc10oaHR0cHM6Ly9odG1sY29sb3Jjb2Rlcy5jb20vY29sb3ItcGlja2VyLykgZS5nLiAjMDAwMDAgaXMgYmxhY2sKLSBgYmFja2dyb3VuZDogcGlua2AgbWFrZXMgdGhlIHRleHQgYmFja2dyb3VuZCBwaW5rLCBhZ2FpbiB5b3UgY2FuIHVzZSBjb2xvciBuYW1lcyBvciBoZXggY29kZXMKLSBgZm9udC1zaXplOiAzMHB0YCBtYWtlcyB0aGUgdGV4dCBzaXplIDMwIHBvaW50LCB5b3UgY2FuIGFsc28gc3BlY2lmeSBvdGhlciBbdW5pdHNdKGh0dHBzOi8vd3d3LmRpZ2l0YWxvY2Vhbi5jb20vY29tbXVuaXR5L3R1dG9yaWFscy9jc3MtY3NzLXVuaXRzLWV4cGxhaW5lZCkKLSBgZm9udC1mYW1pbHk6IG1vbm9zcGFjZWAgbWFrZXMgdGhlIGZvbnQgbW9ub3NwYWNlLCBvdGhlciBbd2ViIHNhZmUgZm9udHNdKGh0dHBzOi8vd3d3Lnczc2Nob29scy5jb20vY3NzcmVmL2Nzc193ZWJzYWZlX2ZvbnRzLnBocCkgY2FuIGJlIHVzZWQKLSBgZm9udC13ZWlnaHQ6IGJvbGRgIG1ha2VzIHRoZSBmb250IGJvbGQsIG90aGVyIFt3ZWlnaHRzXShodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9DU1MvZm9udC13ZWlnaHQpIGNhbiBiZSB1c2VkCi0gYHBvc2l0aW9uOiBhYnNvbHV0ZWAgbWFrZXMgdGhlIHBvc2l0aW9uaW5nIG9mIHRoZSB0ZXh0IGFic29sdXRlIGluIHJlbGF0aW9uIHRvIHRoZSBzY3JlZW4sIGl0IG5lZWRzIGFkZGl0aW9uYWwgcGFyYW1ldGVycyB0byBiZSBzcGVjaWZpZWQgc3VjaCBhcyB0b3AsIHJpZ2h0LCBib3R0b20gb3IgbGVmdAotIGB0b3A6IDBweGAgbWFrZXMgdGhlIGFic29sdXRlIHBvc2l0aW9uaW5nIHRvIGJlIDBweCBmcm9tIHRoZSB0b3AKLSBgbGVmdDogMHB4YCBtYWtlcyB0aGUgYWJzb2x1dGUgcG9zaXRpb25pbmcgdG8gYmUgMHB4IGZyb20gdGhlIGxlZnQKClRoaXMgaXMgd2hhdCBpdCBzaG91bGQgbGlrZToKCiFbXShpbWFnZXMvd29yZF90cmlhbF9jc3MucG5nKQoKIyMgY3VzdG9taXNpbmcgdGhlIHBsdWdpbiBwYXJhbWV0ZXJzCgpBcyB3ZSBkbyBub3QgcmVhbGx5IG5lZWQgbXVjaCBzdHlsaW5nIHRvIG91ciBzdGltdWxpLCB3ZSB3aWxsIHJldHVybiB0byB0aGUgb3JpZ2luYWwgZGVmYXVsdHMgb2Y6CgpgYGB7anN9CnZhciBteV9rZXlib2FyZF90cmlhbCA9IHsKICB0eXBlOiBqc1BzeWNoSHRtbEtleWJvYXJkUmVzcG9uc2UsCiAgc3RpbXVsdXM6ICd3b3JkJywKICBjaG9pY2VzOiBbJ2QnLCAnaCddCn0KCmBgYAoKSG93ZXZlciwgd2hhdCB3ZSBjYW4gZG8gaXMgY2hhbmdlIHNwZWNpZmljIHBhcmFtZXRlcnMgb2Ygb3VyIHRyaWFsLgoKSWYgd2UgcmVmZXIgdG8gdGhlIHBsdWdpbiBkb2N1bWVudGF0aW9uIG9uIHRoZSBqc3BzeWNoIHdlYnNpdGUgLSBodHRwczovL3d3dy5qc3BzeWNoLm9yZy83LjAvcGx1Z2lucy9odG1sLWtleWJvYXJkLXJlc3BvbnNlLyB3ZSBjYW4gc2VlIHRoYXQgdGhlcmUgYXJlIG1hbnkgb3RoZXIgcGFyYW1ldGVycyB0aGF0IHdlIGNhbiBtb2RpZnkgd2l0aCB0aGlzIHBsdWdpbi4KCiMjIyBwcm9tcHQKCldlIG1pZ2h0IGZvciBleGFtcGxlIHdhbnQgdG8gaW5jbHVkZSBhIGBwcm9tcHRgIGlmIHRoZSB0cmlhbHMgYXJlIHByYWN0aWNlIHRyaWFscywgc28gdGhlIHBhcnRpY2lwYW50IGNhbiBiZSByZW1pbmRlZCBvZiB3aGljaCBrZXlzIHRvIHByZXNzOgoKYGBge2pzfQp2YXIgbXlfa2V5Ym9hcmRfdHJpYWwgPSB7CiAgdHlwZToganNQc3ljaEh0bWxLZXlib2FyZFJlc3BvbnNlLAogIHN0aW11bHVzOiAnd29yZCcsCiAgY2hvaWNlczogWydkJywgJ2gnXSwKICBwcm9tcHQ6ICdwcmVzcyAiZCIgaWYgeW91IHRoaW5rIGl0IGlzIGEgcmVhbCB3b3JkLCBwcmVzcyAiaCIgaWYgeW91IHRoaW5rIGl0IGlzIG5vdCBhIHJlYWwgd29yZCcKfQoKYGBgCgpBcyB5b3UgY2FuIHNlZSwgdGhlIHByb21wdCBpcyBkaXNwbGF5ZWQgaW4gYSBxdWl0ZSBpbmNvbnZlbmllbnQgcG9zaXRpb24uIFNvIHdlIGNhbiBub3cgbW9kaWZ5IHRoaXMgdXNpbmcgdGhlIGh0bWwgYW5kIGNzcyBjb2RlIHdlIHVzZWQgcHJldmlvdXNseToKCmBgYHtqc30KdmFyIG15X2tleWJvYXJkX3RyaWFsID0gewogIHR5cGU6IGpzUHN5Y2hIdG1sS2V5Ym9hcmRSZXNwb25zZSwKICBzdGltdWx1czogJ3dvcmQnLAogIGNob2ljZXM6IFsnZCcsICdoJ10sCiAgcHJvbXB0OiAnPGRpdiBzdHlsZT0iZm9udC1zaXplOjEwcHQ7IHBvc2l0aW9uOnJlbGF0aXZlOyBib3R0b206MTUwcHg7Ij5wcmVzcyAiZCIgaWYgeW91IHRoaW5rIGl0IGlzIGEgcmVhbCB3b3JkLCBwcmVzcyAiaCIgaWYgeW91IHRoaW5rIGl0IGlzIG5vdCBhIHJlYWwgd29yZDwvZGl2PicKfQoKYGBgCgpOb3csIHdlIGhhdmUgY2hhbmdlZCB0aGUgZm9udCBzaXplIHRvIGAxMHB0YCBzbyBpdCBpcyBzbWFsbGVyIHRoYW4gdGhlIHN0aW11bHVzIHRleHQuCgpXZSBoYXZlIGFsc28gY2hhbmdlIHRoZSBgcG9zaXRpb25gLCB0aGlzIHRpbWUgdXNpbmcgYHBvc2l0aW9uOnJlbGF0aXZlOyBib3R0b206MTUwcHhgLCB1bmxpa2UgaW4gdGhlIHByZXZpb3VzIGV4YW1wbGUgd2hlcmUgd2UgdXNlZCBgcG9zaXRpb246YWJzb2x1dGVgLCB0aGUgdXNlIG9mIGByZWxhdGl2ZWAgbWVhbnMgd2UgbW92ZSByZWxhdGl2ZSB0byB3aGVyZSBpdCB3YXMgb3JpZ2luYWxseSBwbGFjZWQsIHNvIGluIHRoaXMgZXhhbXBsZSB3ZSBtb3ZlIGl0IGAxNTBweGAgZnJvbSB0aGUgYGJvdHRvbWAsIGkuZS4gMTUwcHggdmVydGljYWxseSB1cHdhcmRzIGZyb20gdGhlIGJvdHRvbS4KClRoaXMgaXMgaG93IGl0IHNob3VsZCBub3cgbG9vazoKCiFbXShpbWFnZXMvd29yZF90cmlhbF9wcm9tcHQucG5nKQoKIyMjIHN0aW11bHVzX2R1cmF0aW9uCgpXZSBtaWdodCBhbHNvIHdhbnQgdG8gb25seSBzaG93IHRoZSBzdGltdWx1cyBvbiB0aGUgc2NyZWVuIGZvciBhIHNwZWNpZmllZCBhbW91bnQgb2YgdGltZS4gVG8gZG8gdGhpcywgd2UgY2FuIGFkZCB0aGUgYHN0aW11bHVzX2R1cmF0aW9uYCBwYXJhbWV0ZXI6CgpgYGB7anN9CnZhciBteV9rZXlib2FyZF90cmlhbCA9IHsKICB0eXBlOiBqc1BzeWNoSHRtbEtleWJvYXJkUmVzcG9uc2UsCiAgc3RpbXVsdXM6ICd3b3JkJywKICBjaG9pY2VzOiBbJ2QnLCAnaCddLAogIHByb21wdDogJzxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxMHB0OyBwb3NpdGlvbjpyZWxhdGl2ZTsgYm90dG9tOjE1MHB4OyI+cHJlc3MgImQiIGlmIHlvdSB0aGluayBpdCBpcyBhIHJlYWwgd29yZCwgcHJlc3MgImgiIGlmIHlvdSB0aGluayBpdCBpcyBub3QgYSByZWFsIHdvcmQ8L2Rpdj4nLAogIHN0aW11bHVzX2R1cmF0aW9uOiA1MDAKfQoKYGBgCgpJbiB0aGUgZXhhbXBsZSBhYm92ZSwgd2UgaGF2ZSBzZXQgdGhpcyBwYXJhbWV0ZXIgdG8gYDUwMGAsIHdoaWNoIG1lYW5zIHRoYXQgdGhlIHN0aW11bHVzIHdpbGwgYmUgc2hvd24gb25seSBmb3IgNTAwbXMuIE5vdGUgdGhhdCB0aGUgdHJpYWwgZG9lcyBub3QgZmluaXNoIHVudGlsIGEgcmVzcG9uc2UgaGFzIGJlZW4gbWFkZS4KCiMjIyB0cmlhbF9kdXJhdGlvbgoKSWYgd2Ugd2FudCB0byBtYWtlIHRoZSB0cmlhbCBlbmQgYWZ0ZXIgYSBzcGVjaWZpZWQgdGltZSwgd2UgY2FuIGFsc28gYWRkIHRoZSBgdHJpYWxfZHVyYXRpb25gIHBhcmFtZXRlcjoKCmBgYHtqc30KdmFyIG15X2tleWJvYXJkX3RyaWFsID0gewogIHR5cGU6IGpzUHN5Y2hIdG1sS2V5Ym9hcmRSZXNwb25zZSwKICBzdGltdWx1czogJ3dvcmQnLAogIGNob2ljZXM6IFsnZCcsICdoJ10sCiAgcHJvbXB0OiAnPGRpdiBzdHlsZT0iZm9udC1zaXplOjEwcHQ7IHBvc2l0aW9uOnJlbGF0aXZlOyBib3R0b206MTUwcHg7Ij5wcmVzcyAiZCIgaWYgeW91IHRoaW5rIGl0IGlzIGEgcmVhbCB3b3JkLCBwcmVzcyAiaCIgaWYgeW91IHRoaW5rIGl0IGlzIG5vdCBhIHJlYWwgd29yZDwvZGl2PicsCiAgc3RpbXVsdXNfZHVyYXRpb246IDUwMCwKICB0cmlhbF9kdXJhdGlvbjogMTAwMAp9CgpgYGAKCk5vdywgdGhlIHRyaWFsIHdpbGwgZW5kIGFmdGVyIGAxMDAwbXNgIG9yIGlmIHRoZXJlIGlzIGEgcmVzcG9uc2UgYnkgdGhlIHBhcnRpY2lwYW50IGluIHdpdGhpbiB0aGUgc3BlY2lmaWVkIHRpbWUuCgpUaGlzIGlzIGhvdyBpdCBzaG91bGQgbG9vazoKCiFbXShpbWFnZXMvd29yZF90cmlhbF9kdXJhdGlvbi5naWYpCgojIyBjb21iaW5pbmcgdHJpYWxzIGludG8gYSB0aW1lbGluZQoKQXQgdGhlIG1vbWVudCBvdXIgZXhwZXJpbWVudCBjb21wcmlzZXMgb2YganVzdCBvbmUgdHJpYWwuLi4sIHdoaWNoIGlzIG5vdCBwYXJ0aWN1bGFybHkgdXNlZnVsLiBIb3dldmVyLCB3aGF0IHdlIGhhdmUgY292ZXJlZCBzbyBmYXIgc2hvdWxkIGFsbG93IHVzIHRvIHB1dCB0b2dldGhlciB0aGUgYnVpbGRpbmcgYmxvY2tzIG9mIGFuIGV4cGVyaW1lbnQuCgpJbiB0aGlzIHNlY3Rpb24gd2Ugd2lsbCBjb21iaW5lIG11bHRpcGxlIHRyaWFscyBpbnRvIGEgdGltZWxpbmUgdGhhdCBpcyBuZXN0ZWQgd2l0aGluIG91ciBtYWluIHRpbWVsaW5lLgoKVGhpcyB3aWxsIGludm9sdmUgYWRkaW5nIGEgbmV3IHZhcmlhYmxlIHRoYXQgaGFzIGEgYGZpeGF0aW9uIHRyaWFsYCwgc28gdGhhdCBvdXIgbGV4aWNhbCBkZWNpc2lvbiB0cmlhbCBpcyBwcmVjZWVkZWQgYnkgdGhpcyBmaXhhdGlvbiB0cmlhbC4KCkxldHMgZmlyc3QgbWFrZSB0aGUgbGV4aWNhbCBkZWNpc2lvbiB0cmlhbCwgd2Ugd2lsbCBrZWVwIGl0IGFzIGp1c3QgYSBrZXlib2FyZCByZXNwb25zZSB3aXRoIG5vIGR1cmF0aW9uIHBhcmFtZXRlcnM6CgpgYGB7anN9CnZhciBsZXhpY2FsX2RlY2lzaW9uX3RyaWFsID0gewogIHR5cGU6IGpzUHN5Y2hIdG1sS2V5Ym9hcmRSZXNwb25zZSwKICBzdGltdWx1czogJ3dvcmQnLAogIGNob2ljZXM6IFsnZCcsICdoJ10sCiAgcHJvbXB0OiAnPGRpdiBzdHlsZT0iZm9udC1zaXplOjEwcHQ7IHBvc2l0aW9uOnJlbGF0aXZlOyBib3R0b206MTUwcHg7Ij5wcmVzcyAiZCIgaWYgeW91IHRoaW5rIGl0IGlzIGEgcmVhbCB3b3JkLCBwcmVzcyAiaCIgaWYgeW91IHRoaW5rIGl0IGlzIG5vdCBhIHJlYWwgd29yZDwvZGl2PicKfQoKYGBgCgpOb3cgd2Ugd2lsbCBtYWtlIHRoZSBmaXhhdGlvbiB0cmlhbCwgd2hpY2ggd2lsbCBzaG93IGEgYCtgIHN0aW11bHVzIG9uIHRoZSBzY3JlZW4gZm9yIGEgc2V0IGFtb3VudCBvZiB0aW1lLCBpbiB0aGlzIGNhc2UgYDIwMDBtc2AuCgpOb3RlIHRoZSBgZm9udC1zaXplYCBpcyBzZXQgdG8gMzBwdCBhbmQgdGhlIGNob2ljZXMgaXMgYE5PX0tFWVNgLCB0aGlzIG1lYW4gd2Ugd2lsbCBqdXN0IHNlZSB0aGUgYCtgIG9uIHRoZSBzY3JlZW4gYW5kIHRoZXJlIHdpbGwgYmUgbm8ga2V5cyB0aGF0IGVuZCB0aGUgdHJpYWwsIGp1c3QgdGhlIHRpbWUgbGltaXQgb2YgYDIwMDBtc2A6CgpgYGB7anN9CnZhciBmaXhhdGlvbl90cmlhbCA9IHsKICB0eXBlOiBqc1BzeWNoSHRtbEtleWJvYXJkUmVzcG9uc2UsCiAgc3RpbXVsdXM6ICc8ZGl2IHN0eWxlPSJmb250LXNpemU6MzBwdDsiPis8L2Rpdj4nLAogIGNob2ljZXM6ICdOT19LRVlTJywKICBwcm9tcHQ6ICc8ZGl2IHN0eWxlPSJmb250LXNpemU6MTBwdDsgcG9zaXRpb246cmVsYXRpdmU7IGJvdHRvbToxNTBweDsiPjxici8+PC9kaXY+JywKICB0cmlhbF9kdXJhdGlvbjogMjAwMAp9CgpgYGAKCldlIGNhbiBub3cgbWFrZSBhIG5ldyB2YXJpYWJsZSBjYWxsZWQgYGxleGljYWxfZGVjaXNpb25fY29tYmluZWRgLCB0aGlzIHdpbGwgY29tYmluZSB0aGUgYGZpeGF0aW9uX3RyaWFsYCBhbmQgdGhlIGBsZXhpY2FsX2RlY2lzaW9uX3RyaWFsYCBpbiBhIHNwZWNpZmllZCBvcmRlciwgd2l0aGluIGEgc3BlY2lmaWVkIHRpbWVsaW5lLgoKV2UgZG8gdGhpcyB1c2luZyB0aGUgYHRpbWVsaW5lYCBwYXJhbWV0ZXIsIHdoaWNoIHdlIGNhbiBzcGVjaWZ5IHdpdGhpbiBhIHN0YW5kYXJkIGB2YXJgLgoKV2UgYXNzaWduIHRoZSB0aW1lbGluZSBhbiBhcnJheSwgYFtmaXhhdGlvbl90cmlhbCwgbGV4aWNhbF9kZWNpc2lvbl90cmlhbF1gLCB3aGljaCBtZWFucyB0aGF0IHRoZSB0aW1lbGluZSB3aWxsIGZpcnN0IHNob3cgdGhlIGBmaXhhdGlvbl90cmlhbGAsIHRoZW4gdGhlIGBsZXhpY2FsX2RlY2lzaW9uX3RyaWFsYC4gSWYgd2UgY2hhbmdlZCB0aGUgcG9zaXRpb25zIG9mIHRoZSBpdGVtcyBpbiB0aGUgYXJyYXkgdGhpcyB3aWxsIGNoYW5nZSB0aGUgb3JkZXIgb2YgdGhlIHByZXNlbnRhdGlvbi4KCmBgYHtqc30KdmFyIGxleGljYWxfZGVjaXNpb25fY29tYmluZWQgPSB7CiAgdGltZWxpbmU6IFtmaXhhdGlvbl90cmlhbCwgbGV4aWNhbF9kZWNpc2lvbl90cmlhbF0KfQoKYGBgCgpJZiB3ZSBub3cgcHVzaCBgbGV4aWNhbF9kZWNpc2lvbl9jb21iaW5lZGAgdG8gb3VyIHRpbWVsaW5lLCB1c2luZyB0aGUgYHRpbWVsaW5lLnB1c2hgLCB3ZSBzaG91bGQgc2VlIHRoYXQgaXQgcHJlc2VudHMgdGhlIHR3byB0cmlhbHMgdG9nZXRoZXIuCgpUaGlzIGlzIGhvdyB5b3VyIGNvZGUgc2hvdWxkIGxvb2sgc28gZmFyOgoKYGBge2pzfQovLyAtLS0tLS0tLQovLyBUaXRsZTogRGVtbyBleHBlcmltZW50Ci8vIGpzUHN5Y2ggdmVyc2lvbjogNy4zLjEKLy8gZGF0ZTogW3RvZGF5XQovLyBhdXRob3I6IFt5b3VyIG5hbWVdCi8vLS0tLS0tLS0tLQoKdmFyIGpzUHN5Y2ggPSBpbml0SnNQc3ljaCgpOwoKdmFyIGZpeGF0aW9uX3RyaWFsID0gewogIHR5cGU6IGpzUHN5Y2hIdG1sS2V5Ym9hcmRSZXNwb25zZSwKICBzdGltdWx1czogJzxkaXYgc3R5bGU9ImZvbnQtc2l6ZTozMHB0OyI+KzwvZGl2PicsCiAgY2hvaWNlczogJ05PX0tFWVMnLAogIHByb21wdDogJzxkaXYgc3R5bGU9ImZvbnQtc2l6ZToxMHB0OyBwb3NpdGlvbjpyZWxhdGl2ZTsgYm90dG9tOjE1MHB4OyI+PGJyLz48L2Rpdj4nLAogIHRyaWFsX2R1cmF0aW9uOiAyMDAwCn0KCnZhciBsZXhpY2FsX2RlY2lzaW9uX3RyaWFsID0gewogIHR5cGU6IGpzUHN5Y2hIdG1sS2V5Ym9hcmRSZXNwb25zZSwKICBzdGltdWx1czogJ3dvcmQnLAogIGNob2ljZXM6IFsnZCcsICdoJ10sCiAgcHJvbXB0OiAnPGRpdiBzdHlsZT0iZm9udC1zaXplOjEwcHQ7IHBvc2l0aW9uOnJlbGF0aXZlOyBib3R0b206MTUwcHg7Ij5wcmVzcyAiZCIgaWYgeW91IHRoaW5rIGl0IGlzIGEgcmVhbCB3b3JkLCBwcmVzcyAiaCIgaWYgeW91IHRoaW5rIGl0IGlzIG5vdCBhIHJlYWwgd29yZDwvZGl2PicKfTsKCnZhciBsZXhpY2FsX2RlY2lzaW9uX2NvbWJpbmVkID0gewogIHRpbWVsaW5lOiBbZml4YXRpb25fdHJpYWwsIGxleGljYWxfZGVjaXNpb25fdHJpYWxdCn0KCgp2YXIgdGltZWxpbmUgPSBbXTsKdGltZWxpbmUucHVzaChsZXhpY2FsX2RlY2lzaW9uX2NvbWJpbmVkKTsKCmpzUHN5Y2gucnVuKHRpbWVsaW5lKTsKYGBgCgoKCgoKCgoKCgoKCmBgYHtyIGVjaG89RkFMU0UsIGV2YWw9VFJVRSwgd2FybmluZz1GQUxTRX0KaHRtbHRvb2xzOjp0YWdzJHNjcmlwdChzcmMgPSAianMvdHJhbnNsYXRlLmpzIikKIyBodG1sdG9vbHM6OnRhZ3Mkc2NyaXB0KHNyYyA9ICJqcy9pbmZvYm94LmpzIikKaHRtbHRvb2xzOjp0YWdzJHNjcmlwdChzcmM9Ii8vdHJhbnNsYXRlLmdvb2dsZS5jb20vdHJhbnNsYXRlX2EvZWxlbWVudC5qcz9jYj1nb29nbGVUcmFuc2xhdGVFbGVtZW50SW5pdCIpCgpodG1sdG9vbHM6OnRhZ0xpc3QoCiAgeGFyaW5nYW5FeHRyYTo6dXNlX2NsaXBib2FyZCgKICAgIGJ1dHRvbl90ZXh0ID0gIjxpIGNsYXNzPVwiZmEgZmEtY2xpcGJvYXJkXCIgc3R5bGU9XCJmb250LXNpemU6IDI1cHhcIj48L2k+IiwKICAgIHN1Y2Nlc3NfdGV4dCA9ICI8aSBjbGFzcz1cImZhIGZhLWNoZWNrXCIgc3R5bGU9XCJjb2xvcjogIzkwQkU2RDsgZm9udC1zaXplOiAyNXB4XCI+PC9pPiIsCiAgKSwKICBybWFya2Rvd246Omh0bWxfZGVwZW5kZW5jeV9mb250X2F3ZXNvbWUoKQopCgpgYGAKCmBgYHtqcyBlY2hvPUZBTFNFLCBldmFsPVRSVUV9CnZhciBjb2xsID0gZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgiY29sbGFwc2libGUxIik7CnZhciBpOwoKZm9yIChpID0gMDsgaSA8IGNvbGwubGVuZ3RoOyBpKyspIHsKICBjb2xsW2ldLmFkZEV2ZW50TGlzdGVuZXIoImNsaWNrIiwgZnVuY3Rpb24oKSB7CiAgICB0aGlzLmNsYXNzTGlzdC50b2dnbGUoImFjdGl2ZTEiKTsKICAgIHZhciBjb250ZW50ID0gdGhpcy5uZXh0RWxlbWVudFNpYmxpbmc7CiAgICBpZiAoY29udGVudC5zdHlsZS5tYXhIZWlnaHQpewogICAgICBjb250ZW50LnN0eWxlLm1heEhlaWdodCA9IG51bGw7CiAgICB9IGVsc2UgewogICAgICBjb250ZW50LnN0eWxlLm1heEhlaWdodCA9IGNvbnRlbnQuc2Nyb2xsSGVpZ2h0ICsgInB4IjsKICAgIH0KICB9KTsKfQoKYGBgCg==